/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.merak.core.web.mvc;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.merak.core.configuration.web.mvc.ExceptionSettings;

public class Dispatcher {

	//~ Attributes ///////////////////////////////////////////////////////////////
	//****************************************************************************
	private ExceptionSettings exceptionSettings = null;
	private PageControllerMap controllerMap     = null;
	private boolean           cacheControl      = true;

	//~ Constructors /////////////////////////////////////////////////////////////
	//****************************************************************************
	public Dispatcher(PageControllerMap controllerMap)
		throws ServletException
	{
		// Configurations
		this.exceptionSettings = MVCManager.getControl().getExceptions();
		this.controllerMap     = controllerMap;
		this.cacheControl      = "true".equals(MVCManager.getView().getCacheControl());
	}

	//~ Methods //////////////////////////////////////////////////////////////////
	//****************************************************************************
	public void dispatchToView(String view,HttpServletRequest req, HttpServletResponse res)
		throws ServletException
	{

		// Just do nothing if there is no view.
		if ( view == null ) return;

		// Set default content type for views
		res.setContentType(
			MVCManager.getView().getContentType()
		);

		// Avoid caching the view based on configs
		if ( ! this.cacheControl ) {
			res.setHeader("Pragma","no-cache, no-store, must-revalidate"); // HTTP 1.0
			res.setHeader("Cache-Control","no-cache"); // HTTP 1.1
			res.setDateHeader ("Expires", 0);          // prevents caching at the proxy server
		}

		// Forward or Redirect
		try {
   			if ( view.startsWith("redirect:") )
       		    res.sendRedirect(view.substring(9));
   			else
       		    req.getRequestDispatcher(view).forward(req,res);
    	}
    	catch (Throwable e) {
    		throw new ServletException("The View has failed to render.",e);
    	}

	}
	//****************************************************************************
	public void dispatchToView(ExceptionView exception,HttpServletRequest req, HttpServletResponse res)
		throws ServletException
	{

    	req.setAttribute("error",exception);
    	req.setAttribute("msg",exception.getMessage());
    	res.setStatus(exception.getHttpError());
    	this.dispatchToView(exception.getView(),req,res);

	}
	//****************************************************************************
	/** The current account has no privileges to execute some action, so the user
	 * must be redirected to a special page */
	public void dispatchBadAccess(HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{
		// Session data
		HttpSession session           = req.getSession();
		String      previousSessionId = req.getRequestedSessionId();

		// Identify current user
		Object account = session.getAttribute(
				MVCManager.getSessionAccountKey()
		);

		try {
			// If the unauthorized account is anonymous
			if (account == null) {
				/* The session may have expired and the user became Anonymous.
				 * A session expires when it is new and browser requests old sessionId.
				 * This user must be redirected to a Expired Session Page. */
				if ( session.isNew() && previousSessionId!=null && !session.getId().equals(previousSessionId) )	{
					this.dispatchExpiredSession(req,res);
				}
				/* Or, the anonymous user is trying to access a resource that
				 * requires login. This user must be redirected to a Login Page. */
				else {
					this.dispatchNoAuthentication(req,res);
				}
			}
			// Else, the unauthorized account is already authenticated
			else {
				/* The user is trying to access a resource that requires more privileges.
				 * In this case, authenticating again will make no difference.
				 * This user must be redirected to a Forbidden Page. */
				this.dispatchNoAuthorization(req,res);
			}
		}
		catch(ServletException e) {
			this.dispatchExecutionError(e, req, res);
		}

	}
	//****************************************************************************
	public void dispatchExecutionError(ServletException e,HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{
		// Send HTTP 400 Bad Request
		// A generic error message, given when no more specific message is suitable.
		res.setStatus(400);

		// If there is a Handler...
		String businessName = this.exceptionSettings.getExecutionErrorLogic();
		if ( businessName != null ) {
			req.setAttribute( "error", e);
			this.runExceptionLogic(businessName, req, res );
		}
		// Else, throw the exception
		else throw e;

	}
	//****************************************************************************
	private void dispatchExpiredSession(HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{
		/* Send HTTP Error: 408 Request Timeout
		 * The server timed out waiting for the request. */
		res.setStatus(408);

		// If there is a Handler...
		String businessName = this.exceptionSettings.getExpiredSessionLogic();
		if ( businessName != null ) {
			this.runExceptionLogic(businessName, req, res );
		}
		// Else, throw an exception about Expired Session
		else throw new ServletException(
			"Your session has expired because of inactivity. Refresh this page or try login again."
		);

	}
	//****************************************************************************
	private void dispatchNoAuthentication(HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{
		/* Send HTTP Error: 401 Unauthorized
		 * Similar to 403 Forbidden, but specifically for use when authentication is possible
		 * but has failed or not yet been provided. */
		res.setStatus(401);

		// If there is a Handler...
		String businessName = this.exceptionSettings.getNoAuthenticationLogic();
		if ( businessName != null ) {
			this.runExceptionLogic(businessName, req, res );
		}
		// Else, throw an exception about No Authentication
		else throw new ServletException(
			"'Anonymous' cannot execute this Action. If login is possible, " +
			"authenticate and try again."
		);

	}
	//****************************************************************************
	private void dispatchNoAuthorization(HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{
		/* Send HTTP Error: 403 Forbidden
		 * The request was a legal request, but the server is refusing to respond to it.
		 * Unlike a 401 Unauthorized response, authenticating will make no difference.*/
		res.setStatus(403);

		// If there is a Handler...
		String businessName = this.exceptionSettings.getNoAuthorizationLogic();
		if ( businessName != null ) {
			this.runExceptionLogic(businessName, req, res );
		}
		// Else, throw an exception about No Authorization
		else throw new ServletException(
			"Your current Account has no sufficient privileges to execute this Action."
		);

	}
	//****************************************************************************
	private void runExceptionLogic(String businessName,HttpServletRequest req,HttpServletResponse res)
		throws ServletException
	{

		String view = this.controllerMap.getController(businessName).runExceptionLogic(req, res);
		this.dispatchToView(view, req, res);

	}
	//****************************************************************************

}
